#include "Server.hpp"

Server* Server::instance = 0;

/** 
 * Initializes a new instance of the Server class.
 */
Server::Server(){
    Server::instance = this;
    
    run = false;
    socket.setBlocking(false);

    maxClients = 10;
    clientTimeout = 1;

	networkHandler.setClientCollection(&clientsList);

    sf::err().rdbuf(cout.rdbuf());
}

/** 
 * Runs the server startup script.
 */
int Server::startup(){
	sEngine.registerClass(clientsList[0]);
    sEngine.registerClass(&clientsList);
    
	sEngine.registerClass(&entityManager);
	Character registerEnt;
    sEngine.registerClass(&registerEnt);
	
    sEngine.registerClass(&regionManager);
    ServerRegion registerRegion;
    sEngine.registerClass(&registerRegion);
    
    sEngine.startup(this);

    return 0;
}

void Server::bind(lua_State* luaState){
    luabind::module(luaState)[
        luabind::class_<Server>("Server")
            .def_readwrite("clientTimeout", &Server::clientTimeout)
            .def_readwrite("maxClients", &Server::maxClients)
            .def_readwrite("inPort", &Server::inPort)
            .def_readwrite("name", &Server::name)
            .def_readwrite("url", &Server::url)
            .def_readwrite("run", &Server::run)
            .def_readonly("clientsList", &Server::clientsList)
			.def_readonly("entityManager", &Server::entityManager)
            .def_readonly("regionManager", &Server::regionManager)
			.def("sendAllChatMessage", &Server::sendAllChatMessage)
            .def("renameClient", &Server::renameClient)
			.def("createEntity", &Server::createEntity)
    ];
    luabind::module(luaState)[
        luabind::def("httpPost", Server::httpPost)
    ];

}

void Server::push(lua_State* luaState){
    luabind::object o = luabind::object(luaState, this);
    o.push(luaState);
}

/** 
 * Starts the server.
 * Server begins accepting connections.
 */
int Server::start(){
    MapLoader::loadMap("New.dat", &map, &regionManager);
    
	clientsList.setup(maxClients, map);
    regionManager.entityManager = &entityManager;
    regionManager.playerManager = &playerManager;

    entityManager.map = map;
	for(int i = 0; i < maxClients; i++){
		clientsList[i]->entity.index = i;
		clientsList[i]->entity.setMap(map);
	}
	playerManager.init(maxClients);
    entityManager.init();

    //Register extra classes for entity and player managers.
    playerManager.registerGlobal("RegionManager", &regionManager);
    entityManager.registerGlobal("RegionManager", &regionManager);
    ServerRegion registerRegion;
    playerManager.registerObject(&registerRegion);
    entityManager.registerObject(&registerRegion);

    ClientCollection::instance = &clientsList;

	startup();

    LoadingClientCollection::instance = &loadingClientsList;

    if(socket.bind(inPort) != sf::Socket::Done){
        return 1;
    }
    cout << "Listening on port " << inPort << endl;
    Log << "Listening on port ";

    run = true;
    time.serverTime.restart();
    MessageQueue::initialize(&time);

    sf::Clock updateTimer;
	sf::Int64 updateAccumulator = 0;
    while(run){ // Main game loop
        
        frameTime = time.getTime();
        updateAccumulator += updateTimer.getElapsedTime().asMicroseconds();
        updateTimer.restart();

        handleConnection();
        handleTimeouts();
        updateClientQueues();
        
        while(updateAccumulator >= 20000){
            update();
            updateAccumulator -= 20000;
        }
        
		sf::sleep(sf::milliseconds(1));
    }
    shutdown();
    
    return 0;
}

/**
 * Called to stop server nicely.
 * This will stop the loop and exit program naturally.
 */
void Server::stop(){
    run = false;
    Log << "Server stopping.\n";
}

/**
 * Called when server is shutting down.
 */
void Server::shutdown(){
    Log << "Server shutting down.\n";
    stop();
    sEngine.shutdown();
    socket.unbind();
    Log.close();
}
    

/** 
* Ping the server tracker to list this server.
*/
void Server::httpPost(const char* host, const char* addr, const char* msg){

    //Build a http post request
    sf::Http http;
    sf::Http::Request httpRequest;
    httpRequest.setMethod(sf::Http::Request::Post);
    httpRequest.setUri(addr);
    httpRequest.setField("Content-type", "text/csv");

    //Write server data
    httpRequest.setBody(msg);
    http.setHost(host);

    //Send post, do not wait for response.
    sf::Http::Response response = http.sendRequest(httpRequest, sf::seconds(3));
}

/** 
* Handles incoming network messages.
*/
void Server::handleConnection(){
    Packet rPacket;
    IpAddress rIp;
    unsigned short rPort;

    //Check if a valid packet has been sent
    if(socket.receive(rPacket, rIp, rPort) != socket.Done || rPacket.getDataSize() < MIN_PACKET_SIZE){
        return;
    }

    //Parse packet into messages
    globalInQueue.parsePacket(rPacket);
	if(globalInQueue.size == 0){
        return;
    }

    cout << rIp.toString() << ": " << globalInQueue.size << " message(s) received." << endl;
	MessageIterator message = globalInQueue.begin();
    switch((*message)->group){
		case Message::g_Normal:
            handleClientConnection(message, rIp);
			break;
		case Message::g_Critical:
			handleClientConnection(message, rIp);
			break;
        case Message::g_NewConnection:
			if((*message)->code == Message::m_c_ConnectionRequest){
                handleNewConnection(message, rIp, rPort);
            }
			break;
        case Message::g_Loading:
            handleLoadingConnection(message, rIp);
			break;
    }

	globalInQueue.clearQueue();
}

/** 
* Check if any connections have timed out.
*/
void Server::handleTimeouts(){

    //Check loading clients
    LoadingClientCollection::LoadingClientNode* loadingClient = loadingClientsList.clientList;
    while(loadingClient != 0){
        LoadingClientCollection::LoadingClientNode* nextClient = loadingClient->next;
        if(loadingClient->timeout->getElapsedTime().asMilliseconds() > 3000){
            cout << loadingClient->address->toString() << ": Loading client timed out." << endl;
            loadingClientsList.removeClient(loadingClient);
        }
        loadingClient = nextClient;
    }

    //Check connected clients
	for(byte i = 0; i < clientsList.MAX_CLIENTS; i++){
		if(clientsList[i]->active && clientsList[i]->lastMessage.getElapsedTime().asMilliseconds() > clientTimeout){
            //Run timeout script
            sEngine.clientTimeout(clientsList[i]);
	        //Disconnect the client from server.
            disconnectClient(i);
        }
    }

}

/**
 * Disconnect a connected client from the server.
 * Removes client and all associated client data.
 * Closes connection between server and client.
 * @param client Index of client in the ClientCollection.
 */
void Server::disconnectClient(byte client){
    if(clientsList[client]->active){
        std::cout << "Disconnecting Client: " << (int)client << std::endl;
        //Destroy client's player entity and region
        playerManager.destroyPlayer(&clientsList[client]->entity);
        regionManager.destroyRegion(clientsList[client]->entity.region);
        clientsList[client]->entity.region = 0;
	    
        //Mark client as inactive in the ClientCollection
        clientsList.removeClient(client);
        //Send remove message to all other clients.
	    networkHandler.sendAllMessage(Message::m_b_ClientDisconnect, &client);
    }
}

/** 
* Handles connections that have been accepted by the server but not yet added to the clients list.
* The server is waiting for these connections to notify that they are ready to be added to the game world.
*/
void Server::handleLoadingConnection(MessageIterator& iMessage, const sf::IpAddress& address){

    LoadingClientCollection::LoadingClientNode* loadingClient = loadingClientsList.clientList;

    //While loop to find the corresponding client
    while(loadingClient != 0){
        LoadingClientCollection::LoadingClientNode* nextClient = loadingClient->next;

        if(loadingClient->address->toInteger() == address.toInteger()){

            if((*iMessage)->code == Message::m_c_ClockSyncReq){
				uint32_t time = MessageQueue::serverTime->getTime();
				globalOutQueue.createMessage(Message::m_s_ClockSyncResp, &time);
                loadingClient->timeout->restart();
                globalOutQueue.forceSendQueue(socket, *loadingClient->address, loadingClient->port);
                globalOutQueue.clearQueue();
                cout << "Clock sync request" << endl;
            }else if((*iMessage)->code == Message::m_c_LoadingComplete){
                //Client connected add to game
                Client* addClient = clientsList.addClient();
				playerManager.createPlayer(&addClient->entity);
				addClient->setup(loadingClient->address->toString(), *loadingClient->address, loadingClient->port);
                addClient->entity.region = regionManager.createRegion("player");

                loadingClientsList.removeClient(loadingClient);
                sEngine.clientConnected(addClient);

                //Send client add reply
				globalOutQueue.createMessage(Message::m_s_ClientAdd, &addClient->index);
                
                //Send client game state
                Packet_GameState state;
                state.maxClients = clientsList.MAX_CLIENTS;
				globalOutQueue.createMessage(Message::m_s_GameState, &state);

                //Send client all client states.
                int clientCounter = 0;
				for(int i = 0; i < clientsList.MAX_CLIENTS; i++){
					if(clientsList[i]->active){
						Packet_ClientState clientState = PacketHelper::ClientState(&clientsList[i]->entity, clientsList[i]->name.c_str());
						globalOutQueue.createMessage(Message::m_s_NewClient, &clientState);
                        
                        if(clientCounter == addClient->index){
                            networkHandler.sendAllMessageExcept(Message::m_s_NewClient, &clientState, addClient->index);
                        }
						clientCounter++;
					}
                }

				//Send client all entity states
				for(int i = 0; i < entityManager.size; i++){
					if(entityManager.entities[i] != 0){
						Packet_EntityState entityState = PacketHelper::EntityState(entityManager.entities[i]);
						globalOutQueue.createMessage(Message::m_s_NewEntity, &entityState);
					}
				}

                globalOutQueue.forceSendQueue(socket, addClient->address, addClient->port);
                globalOutQueue.clearQueue();
                addClient->lastMessage.restart();
            }

            return;
        }
        loadingClient = nextClient;
    }

}

/** 
* Handles incoming connection requests.
* If the server accepts the incoming connection, an accept message is sent to the client.
* The client is added to the loading list.
*/
void Server::handleNewConnection(MessageIterator& iMessage, const sf::IpAddress& address, const unsigned short& port){

    if((*iMessage)->code == Message::m_c_ConnectionRequest && clientsList.getClientCount() + loadingClientsList.getClientCount() < maxClients){

        //Run connection_request script function. If it returns true accept connection.
        if(sEngine.connectionRequest(address.toString().c_str())){
            //Send accept information
			globalOutQueue.createMessage(Message::m_s_ConnectionAccept, 0);
            //If accept message was successfully sent, add connection to loading client list.
            //Else reject client
            if(globalOutQueue.forceSendQueue(socket, address, port) == Socket::Done){
                loadingClientsList.addClient(address, port);
            }
			globalOutQueue.clearQueue();
            return;
        }
    }

    //If we reached here then either server is full or connection request
    //was rejected.Send reject message
	globalOutQueue.createMessage(Message::m_s_ConnectionRefuse, 0);
    globalOutQueue.forceSendQueue(socket, address, port);
    globalOutQueue.clearQueue(); //Clear regardless of whether message arrived or not.
}

/** 
* Handles connections that are accepted clients of the server.
*/
void Server::handleClientConnection(MessageIterator& iMessage, const sf::IpAddress& address){
    byte index;
    if((*iMessage)->code == Message::m_c_SenderInfo){
        index = *(byte*)(*iMessage)->buffer;
        //Check if sender is valid
        if(index >= clientsList.MAX_CLIENTS || clientsList[index] == 0 || clientsList[index]->address.toInteger() != address.toInteger()){
            return;
        }
    }
    
    Client* client = clientsList[index];
    client->lastMessage.restart();
    uint32_t serverTime = time.getTime();
    ++iMessage;

    while((*iMessage)){
		Message* message = (*iMessage);
        uint32_t delta = serverTime - message->serverTime;
        cout << delta << "ms. ";
        switch(message->code){
            case Message::m_b_ChatMessage:{
				Packet_ChatMessage* clientMessage = (Packet_ChatMessage*)message->buffer;
                
                if(sEngine.chatMessageReceived(clientMessage->message, clientsList[client->index])){
					Packet_ChatMessage serverMessage = PacketHelper::ChatMessage(clientMessage->message, client->index);
                    networkHandler.sendAllMessage(Message::m_b_ChatMessage, &serverMessage);
				}
                
                break;
            }
            case Message::m_c_PlayerMove: {
                byte* direction = ((byte*)message->buffer);
                if(client->entity.lastNetworkUpdateTime > message->serverTime){
                    cout << "Discarding old Player Move message.";
                    break;
                }
                client->entity.lastNetworkUpdateTime = message->serverTime;
                cout << "Player Move: " << (int)*direction;
                
                client->entity.setMove(*direction);
				Packet_ServerEntityLocation sEl = PacketHelper::ServerEntityLocation(&client->entity);
				networkHandler.sendAllMessage(Message::m_s_PlayerMove, &sEl);
                break;
            }
            case Message::m_b_ClientDisconnect: {
                disconnectClient(client->index);
                break;
            }

        }
        cout << endl;
		++iMessage;
    }

}

/** 
* Update server state.
*/
void Server::update(){
    sEngine.update(frameTime);
	for(int i = 0; i < clientsList.MAX_CLIENTS; i++){
		if(clientsList[i]->active)
            clientsList[i]->entity.update();
    }
	entityManager.update(frameTime);
    regionManager.update(frameTime);
}

void Server::renameClient(byte client, const char* name){
    Packet_ClientRename renameMessage;
    renameMessage.client = client;
    strcpy(renameMessage.name, name);
    networkHandler.sendAllMessage(Message::m_s_Rename, &renameMessage);
}

Character* Server::createEntity(const char* name){
	Character* entity = (Character*)entityManager.createEntity(name);

    if(entity){
		Packet_EntityState entityState = PacketHelper::EntityState(entity);
		networkHandler.sendAllMessage(Message::m_s_NewEntity, &entityState);
	}
    return entity;
}

/** 
* Sends any message queues that are ready to be sent.
*/
void Server::updateClientQueues(){
	for(int i = 0; i < clientsList.MAX_CLIENTS; i++){
		if(clientsList[i]->active)
			clientsList[i]->outQueue.sendQueue(socket, clientsList[i]->address, clientsList[i]->port);
    }
}

/** 
* Sends all clients a chat message.
* @param message The message to send.
*/
void Server::sendAllChatMessage(const char* message){
	networkHandler.sendAllChatMessage(message);
}

Server::~Server(){
    instance = 0;
}
